import time
import tkinter as tk
from tkinter import ttk
# from tkinter import filedialog
import threading

# 导入数据请求模块
import requests
# 导入正则表达式模块
import re
# 导入文件操作模块
import os
# 导入自动化模块
from DrissionPage import ChromiumPage
# from DrissionPage import ChromiumOptions
#from DrissionPage.common import Actions
# 导入url编码模块
from urllib.parse import quote
from openpyxl import Workbook

g_browser_handle = None  # 保存全局浏览器句柄


# 获取单个帖子内容
def get_single_content(uper, uper_id, uper_token, srcurl, is_sanlian, is_pic, is_link):
    cnt_list = []
    # 构造标头header
    headers = {
        # cookies
        'Cookie': 'code your cookie here',
        # referer 防盗链, 请求网址从哪里跳转过来
        'referer': srcurl,
        # user-agent 用户代理, 表示浏览器/设备基本身份信息
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 '
                      'Safari/537.36 '
    }

    # 构造单个帖子的URL
    sin_url = f'https://www.xiaohongshu.com/explore/{uper_id}?xsec_token={uper_token}&xsec_source=pc_search'

    # 发送请求
    mesg_text.set("循环检索中...请求单条笔记内容")
    response = requests.get(url=sin_url, headers=headers)

    # 获取响应的文本数据
    html = response.text
    mesg_text.set("循环检索中...提取笔记相关信息")
    old_title = re.findall('<meta name="og:title" content="(.*?)">', html)[0]  # 提取帖子标题
    note_title = re.sub(r'[\\/:*?"<>|\n]', '_', old_title)  # 替换特殊字符
    note_content = re.findall('<meta name="description" content="(.*?)">', html)[0]  # 提取帖子内容
    note_keywords = re.findall('<meta name="keywords" content="(.*?)">', html)[0]  # 提取帖子关键词

    cnt_list.append(uper)  # 添加博主ID
    cnt_list.append(note_title)  # 添加笔记标题
    cnt_list.append(note_keywords)  # 添加笔记关键词

    if is_sanlian:
        note_like = re.findall('<meta name="og:xhs:note_like" content="(.*?)">', html)[0]  # 提取帖子点赞数
        note_collect = re.findall('<meta name="og:xhs:note_collect" content="(.*?)">', html)[0]  # 提取帖子收藏数
        note_comment = re.findall('<meta name="og:xhs:note_comment" content="(.*?)">', html)[0]  # 提取帖子评论数
        cnt_list.append(note_like)  # 添加笔记点赞数
        cnt_list.append(note_collect)  # 添加笔记收藏数
        cnt_list.append(note_comment)  # 添加笔记评论数
    else:
        cnt_list.append(0)  # 添加笔记点赞数
        cnt_list.append(0)  # 添加笔记收藏数
        cnt_list.append(0)  # 添加笔记评论数

    if is_link:
        cnt_list.append(sin_url)  # 添加笔记链接
    else:
        cnt_list.append("https://www.xiaohongshu.com/explore")  # 添加笔记链接

    cnt_list.append("笔记内容")  # 添加笔记内容

    if is_pic:
        mesg_text.set("循环检索中...下载图片中")
        note_img_list = re.findall('<meta name="og:image" content="(.*?)">', html)  # 提取帖子图片链接
        # 定义序号
        num = 1

        # 创建以标题命名的目录
        root_img_dir = f"img"
        if not os.path.exists(root_img_dir):
            os.mkdir(root_img_dir)

        # 创建以标题命名的目录
        sin_img_dir = f"img\\{note_title[:10]}"
        if not os.path.exists(sin_img_dir):
            os.mkdir(sin_img_dir)

        # for循环遍历, 提取列表里面的元素内容
        for img_url in note_img_list:
            # 对于图片链接发送请求, 获取二进制数据
            img_content = requests.get(url=img_url).content
            # 创建序列号自增文件名
            img_path = f"{sin_img_dir}\\{num}.jpg"
            with open(img_path, 'wb') as f:
                # 写入数据
                f.write(img_content)
            num += 1

    # 返回内容列表
    return cnt_list


font_style = {
    'family': '微软雅黑',
    'size': 12,
    'weight': 'bold'
}

root = tk.Tk()
root.title("小红书爬虫")
root.eval('tk::PlaceWindow . center')  # 居中窗口
root.resizable(False, False)  # 禁用窗口大小调整

# 第一行
tk.Label(root, text="关键词", font=font_style).grid(row=0, column=0, padx=5, pady=5)
keyword = tk.Entry(root)
keyword.insert(0, "大有泰")
keyword.grid(row=0, column=1, columnspan=4, padx=5, pady=5, sticky="ew")

# 第二行 搜索上限 和 配置项
tk.Label(root, text="搜索上限", font=font_style).grid(row=1, column=0, padx=5, pady=5)
limitcount = tk.Entry(root)
limitcount.insert(0, "50")
limitcount.grid(row=1, column=1, padx=5, pady=5)

# 默认:博主、内容、关键词
# 可选:三连、图片、链接
sanlian_var = tk.BooleanVar(value=True)
tk.Checkbutton(root, text="三连", variable=sanlian_var, font=font_style).grid(row=1, column=2, sticky="w")

pic_var = tk.BooleanVar(value=True)
tk.Checkbutton(root, text="图片", variable=pic_var, font=font_style).grid(row=1, column=3, sticky="w")

link_var = tk.BooleanVar(value=True)
tk.Checkbutton(root, text="链接", variable=link_var, font=font_style).grid(row=1, column=4, sticky="w")

# 第三行   搜索进度
tk.Label(root, text="搜索进度", font=font_style).grid(row=2, column=0, padx=5, pady=5)
progress_bar = ttk.Progressbar(root, mode="determinate")
progress_bar.grid(row=2, column=1, columnspan=3, padx=5, pady=5, sticky="ew")
progress_text = tk.StringVar(value="0%")
tk.Label(root, textvariable=progress_text, font=font_style).grid(row=2, column=4, padx=5, pady=5)

# 全局参数
stop_event = threading.Event()
running_thread = None  # 保存线程对象


# 主线程
def long_task():
    mesg_text.set("打开浏览器")
    # 打开浏览器 (实例化浏览器对象)
    g_browser_handle = ChromiumPage()
    mesg_text.set("监听数据包")
    # 监听数据包
    g_browser_handle.listen.start('web/v1/search/notes')

    # 关键词url转码
    key_word_url = quote(keyword.get())
    req_url = f"https://www.xiaohongshu.com/search_result?keyword={key_word_url}&source=web_explore_feed&type=51"

    # 访问网站请求关键词搜素 --  期间需要扫码登录
    mesg_text.set("搜索关键词")
    g_browser_handle.get(req_url)
    mesg_text.set("请扫码登录请求搜索")
    ret = g_browser_handle.listen.wait()  # 等待数据包加载
    json_data = ret.response.body  # 获取响应数据 -> 字典数据
    mesg_text.set("提取数据包")
    items = json_data['data']['items']  # 键值对取值, 提取ID和token所在列表
    count = 0  # 遍历计数
    sin_content = []
    print(f"默认采集:{len(items)}, 上限:{int(limitcount.get())}")
    total_count = min(len(items), int(limitcount.get()))  # 获取搜索上限

    # 创建表格工作区
    mesg_text.set("创建存储表格")
    wb = Workbook()
    ws = wb.active  # 获取默认工作表
    ws.title = "搜索结果"  # 重命名工作表
    ws.append(["博主ID", "笔记标题", "关键词", "点赞", "收藏", "评论", "笔记链接", "笔记内容"])  # 写入标题行

    # for循环遍历, 提取列表里面的元素
    for item in items:
        mesg_text.set("循环检索中...")
        try:
            # 线程停止命令判断
            if stop_event.is_set():
                print(f"搜索线程异常退出!")
                mesg_text.set("终止检索")
                exit()

            # 进度计算
            count = count + 1

            # 搜索条数限制
            if count > total_count:
                print(f"搜索数量已达到上限{count}条")
                mesg_text.set("搜索数量已达上限")
                break

            # 提取ID
            id_ = item['id']
            # 提取博主昵称
            uper = item['note_card']['user']['nick_name']
            print(f"uper:{uper}")
            # 提取token
            token = item['xsec_token']

            # 获取帖子内容
            sin_content.clear()
            sin_content = get_single_content(uper=uper, uper_id=id_, uper_token=token, srcurl=req_url,
                                             is_sanlian=sanlian_var, is_pic=pic_var, is_link=link_var)
            # 追加到表格中
            ws.append(sin_content)

            # 更新进度
            cur_progress = (100 * count) // total_count
            progress_bar['value'] = cur_progress
            progress_text.set(f"{cur_progress}%")

        except Exception as e:
            print(e)

    # 保存搜索结果到表格
    mesg_text.set("存储数据到表格")
    xls_name = f"{keyword.get()}.xlsx"
    wb.save(xls_name)
    btn_var.set("开始")
    keyword.config(state="normal")
    mesg_text.set("检索完成")


def btn_func():
    global running_thread
    thread_param = {}
    if btn_var.get() == "开始":
        if keyword.get() == "":
            messagebox.showerror("错误", "请输入搜索关键词")
            return
        btn_var.set("结束")
        keyword.config(state="disable")
        if not running_thread or not running_thread.is_alive():
            stop_event.clear()  # 重置停止标志
            progress_bar['value'] = 0
            running_thread = threading.Thread(target=long_task, daemon=True)
            running_thread.start()
            mesg_text.set("开始")
    else:
        btn_var.set("开始")
        keyword.config(state="normal")
        if running_thread and running_thread.is_alive():
            stop_event.set()  # 设置停止标志


# 第四行 启停按钮
btn_var = tk.StringVar()
btn_var.set("开始")
tk.Button(root, textvariable=btn_var, command=btn_func, font=font_style).grid(row=3, column=0, columnspan=5, padx=5, pady=5, sticky="ew")


# 第五行 提示信息
mesg_text = tk.StringVar(value="就绪")
tk.Label(root, textvariable=mesg_text).grid(row=4, column=0, columnspan=5, padx=5, pady=5, sticky="w")

root.mainloop()
